<?php

declare(strict_types=1);

namespace Zms\ImgCaptcha;

class Captcha
{
    /**
     * 验证码配置
     *
     * @var array{char_preset:string,length:int,width:int,height:int,font:string,bg:array,use_noise:bool}
     * - char_preset: (string) 字符串，包含用于生成验证码的所有字符，默认为数字和大小写字母
     * - length: (int) 整数，验证码字符的数量
     * - width: (int) 图片宽度，单位为像素
     * - height: (int) 图片高度，单位为像素
     * - font: (string) 字体文件路径，为空时使用系统默认字体
     * - bg: (array) 背景颜色的RGB值数组
     * - use_noise: (bool) 是否在图片上添加噪音点
     */
    private $config = [
        'char_preset' => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', // 字符集，由数字和大小写字母组成
        'length' => 4, // 验证码位数，设定为4位
        'width' => 120, // 验证码图片宽度，单位为像素
        'height' => 34, // 验证码图片高度，单位为像素
        'font' => '', // 字体文件路径，若为空则使用默认字体
        'bg' => [243, 251, 254], // 背景色，RGB格式数组
        'use_noise' => true // 是否使用背景噪音，布尔值
    ];

    private $textColor = [];//字体颜色
    private $_code = '';//验证码


    /**
     * 图片标识符
     * @var resource
     */
    private $im = null;

    /**
     * 验证码图片宽度
     * @var int
     */
    protected $imageWidth = 0;

    /**
     * 验证码图片高度
     * @var int
     */
    protected $imageHeight = 0;


    /**
     * 获取验证码实例
     * @param array $config
     * @return Captcha
     */
    public static function instance(array $config = []): Captcha
    {
        return new self($config);
    }


    /**
     * @param array{char_preset:string,length:int,width:int,height:int,font:string,bg:array,use_noise:bool} $config
     * - char_preset: (string) 字符串，包含用于生成验证码的所有字符，默认为数字和大小写字母
     * - length: (int) 整数，验证码字符的数量
     * - width: (int) 图片宽度，单位为像素
     * - height: (int) 图片高度，单位为像素
     * - font: (string) 字体文件路径，为空时使用系统默认字体
     * - bg: (array) 背景颜色的RGB值数组
     * - use_noise: (bool) 是否在图片上添加噪音点
     */
    public function __construct(array $config = [])
    {
        foreach ($config as $key => $val) {
            if (isset($this->config[$key])) {
                $this->config[$key] = $val;
            }
        }
    }


    /**
     * @return  array{char_preset:string,length:int,width:int,height:int,font:string,bg:array,use_noise:bool}
     * - char_preset: (string) 字符串，包含用于生成验证码的所有字符，默认为数字和大小写字母
     * - length: (int) 整数，验证码字符的数量
     * - width: (int) 图片宽度，单位为像素
     * - height: (int) 图片高度，单位为像素
     * - font: (string) 字体文件路径，为空时使用系统默认字体
     * - bg: (array) 背景颜色的RGB值数组
     * - use_noise: (bool) 是否在图片上添加噪音点
     */
    public function getConfig(): array
    {
        return $this->config;
    }


    /**
     * 创建验证码图像
     * @return object|false|\GdImage|resource|null
     */
    public function create()
    {
        $this->_code = $this->generate();

        $this->imageWidth = $this->config['width'];
        $this->imageHeight = $this->config['height'];

        $font_size = intval($this->config['height'] * 0.66);


        // 图片宽(px)
        if (!$this->imageWidth) {
            $this->imageWidth = intval(ceil($this->config['length'] * $font_size + ($this->config['length'] + 1) * 10));
        }
        // 图片高(px)
        if (!$this->imageHeight) {
            $this->imageHeight = intval(ceil($font_size + 20));
        }

        // 建立一幅 $this->imageW x $this->imageH 的图像
        if (function_exists('imagecreatetruecolor')) {
            $this->im = imagecreatetruecolor((int)$this->imageWidth, (int)$this->imageHeight);
        } else {
            $this->im = imagecreate($this->imageWidth, $this->imageHeight);
        }

        // 背景色
        $background_color = imagecolorallocate($this->im, $this->config['bg'][0], $this->config['bg'][1], $this->config['bg'][2]);
        // 画一个方形并填背景色
        imagefill($this->im, 0, 0, $background_color);

        // 验证码字体随机颜色
        for ($i = 0; $i < $this->config['length']; $i++) {
            $this->textColor[] = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
        }
        $font = $this->config['font'] ?: (dirname(__DIR__) . '/assets/code-font.otf');
        if ($this->config['use_noise']) {
            // 绘杂点
            $this->writeNoise();
        }
        $texts = preg_split('//u', $this->_code, -1, PREG_SPLIT_NO_EMPTY); // 验证码
        $mx = intval(ceil(($this->imageWidth - $font_size * $this->config['length']) / ($this->config['length'] + 1)));
        $my = intval(ceil(($this->imageHeight - $font_size) / 2));
        $mx = max($mx, 0);
        $my = $my > 0 ? $my : 5; // 上边距为负数时，修改上边距随机最大数为5
        $x = $mx;
        $left = intval(($this->config['width'] / $this->config['length'] - $font_size) * 0.6);
        if ($left <= 0) {
            $left = 0;
        }
        foreach ($texts as $i => $char) {
            $y = intval(ceil(($font_size + mt_rand(0, $my))));
            $angle = mt_rand(-5, 5);
            imagettftext($this->im, $font_size, $angle, $x, $y, $this->textColor[$i], $font, $char);
            if ($i === 0) {
                $x += intval($left * 0.5) + $font_size + mt_rand(0, $mx);
            } else {
                $x += $left + $font_size + mt_rand(0, $mx);
            }
        }
        return $this->im;
    }


    /**
     * 获取和创建验证码
     * @return array{image:string,code:string}|null
     */
    public function getCaptcha()
    {
        if (gettype($this->create()) === 'object') {
            ob_start();
            imagepng($this->im);
            $imgContent = ob_get_contents();
            ob_end_clean();
            imagedestroy($this->im);
            $this->im = null;
            return [
                'image' => 'data:image/png;base64,' . base64_encode($imgContent),
                'code' => $this->_code
            ];
        }
        return null;
    }


    /**
     * 创建验证码
     * @return string
     */
    private function generate(): string
    {
        $char = '';
        $characters = preg_split('//u', $this->config['char_preset'], -1, PREG_SPLIT_NO_EMPTY);
        for ($i = 0; $i < $this->config['length']; $i++) {
            $char .= $characters[mt_rand(0, count($characters) - 1)];
        }
        return $char;
    }


    /**
     * 画杂点
     */
    private function writeNoise()
    {
        $codeSet = '**************************************************';
        $m = intval(ceil($this->imageWidth / 20));
        for ($i = 0; $i < $m; $i++) {
            //杂点颜色
            $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
            for ($j = 0; $j < 5; $j++) {
                // 绘杂点
                imagestring($this->im, mt_rand(1, 5), mt_rand(-10, $this->imageWidth), mt_rand(-10, $this->imageHeight), $codeSet[mt_rand(0, 29)], $noiseColor);
            }
        }
    }
}